﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections.ObjectModel;
using System.IO;
using System.Diagnostics;
using System.Xml;
using System.Windows;
using RunCodeInAppDomain;
using AssemblyLoader;

namespace SpringBeansEditor.Providers
{
    public class TypesProvider
    {
        static TypesProvider _main_instance;
        public delegate void AssembliesLoadedDelegate();
        public delegate ObservableCollection<TypeInfo> LoadAssembliesDelegate(string[] paths);
        public AssembliesLoadedDelegate OnAssembliesLoaded { get; set; }

        ObservableCollection<TypeInfo> _types_available = new ObservableCollection<TypeInfo>();
        Dictionary<string, TypeInfo> _types_used;
        Dictionary<TypeInfo, List<string>> _objects_by_type;
        AppDomain _reflection_domain;

        public ObservableCollection<TypeInfo> GetTypesAvailable()
        {
            return _types_available;
        }

        public static TypesProvider GetInstance()
        {
            if (_main_instance == null)
                _main_instance = new TypesProvider();
            return _main_instance;
        }

        protected TypesProvider()
        {
            _reflection_domain = AppDomain.CreateDomain("MyReflectionDomain", null);

            _types_used = new Dictionary<string, TypeInfo>();
            _objects_by_type = new Dictionary<TypeInfo, List<string>>();
        }

        public void AddTypeUsed(string object_id, string object_type_name)
        {
            TypeInfo object_type = (from type in _types_available where type.Name == object_type_name select type).FirstOrDefault();

            if (object_type != null)
            {
                _types_used[object_id] = object_type;
                if (!_objects_by_type.ContainsKey(object_type))
                {
                    _objects_by_type[object_type] = new List<string>();
                }
                _objects_by_type[object_type].Add(object_id);
                _objects_by_type[object_type].Sort();
            }
        }

        /// <summary>
        /// Clears any type and object id registered and adds the objects defined 
        /// in the xml document passed.
        /// </summary>
        /// <param name="spring_xml_doc"></param>
        public void AddTypesUsed(XmlDocument spring_xml_doc)
        {
            _types_used.Clear();
            _objects_by_type.Clear();

            string type = "";
            XmlNodeList obj_elems = spring_xml_doc.GetElementsByTagName("object");
            foreach (XmlNode elem in obj_elems)
            {
                string[] fulltype = elem.Attributes["type"].Value.Split(',');
                if (fulltype.Length > 1)
                {
                    type = fulltype[0];
                }
                AddTypeUsed(elem.Attributes["id"].Value, type);
            }
        }

        public List<TypePropertyInfo> GetTypeProperties(string object_id)
        {
            List<TypePropertyInfo> properties = new List<TypePropertyInfo>();
            if (_types_used.ContainsKey(object_id))
            {
                properties = _types_used[object_id].Properties;
            }
            return properties;
        }

        public void RemoveTypeUsed(string object_id, string object_type_name)
        {
            TypeInfo object_type = (from type in _types_available where type.Name == object_type_name select type).FirstOrDefault();
            _types_used.Remove(object_id);
            if (object_type != null)
            {
                if (!_objects_by_type.ContainsKey(object_type))
                {
                    _objects_by_type[object_type] = new List<string>();
                }
                _objects_by_type[object_type].Remove(object_id);
            }
        }

        /// <summary>
        /// Loads the assemblies found in the paths passed and gets all 
        /// the types from those assemblies.
        /// </summary>
        /// <param name="paths"></param>
        public void LoadAssemblies(string[] paths)
        {
            //Unload previouly loaded assemblies
            AppDomain.Unload(_reflection_domain);
            //_reflection_domain.pr
            //Setup reflection domain
            AppDomainSetup setup = new AppDomainSetup();
            // setup.ApplicationBase = AppDomain.CurrentDomain.;
            setup.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;
            setup.ApplicationName = "AssemblyLoader";
            //setup.ShadowCopyFiles = "true";
            //setup.ShadowCopyDirectories = Path.GetTempPath();

            _reflection_domain = AppDomain.CreateDomain("AssemblyLoader", null, setup);
            _types_available = (ObservableCollection<TypeInfo>)DomainRunner.RunInAppDomain(new LoadAssembliesDelegate(AssemblyLoader.AssemblyLoader.LoadAssemblies), _reflection_domain, new
                object[] { paths });
            //Assembly[] assemblies_reflected = _reflection_domain.ReflectionOnlyGetAssemblies();
            //AssemblyLoader.AssemblyLoader remoteLoader = (AssemblyLoader.AssemblyLoader)
            //    _reflection_domain.CreateInstanceFromAndUnwrap(Assembly.GetAssembly(typeof(AssemblyLoader.AssemblyLoader)).CodeBase,
            //    "AssemblyLoader.AssemblyLoader");

            //_types_available = remoteLoader.LoadAssemblies(paths);
            RefreshTypes();
            OnAssembliesLoaded();
        }


        /// <summary>
        /// Re-populates the types registered with updated Type objects. Call
        /// this method after loading new asemblies.
        /// </summary>
        public void RefreshTypes()
        {
            List<string[]> object_id_type_pairs = new List<string[]>();
            foreach (var object_id in _types_used.Keys)
            {
                object_id_type_pairs.Add(new string[] { object_id, _types_used[object_id].Name });
            }
            _types_used.Clear();
            _objects_by_type.Clear();
            foreach (string[] object_id_type_pair in object_id_type_pairs)
            {
                AddTypeUsed(object_id_type_pair[0], object_id_type_pair[1]);
            }
        }



        //public void AddAssembly(string path)
        //{
        //    Assembly curr_assembly;
        //    Type[] types_found_in_assembly;
        //    curr_assembly = Assembly.ReflectionOnlyLoadFrom(path);
        //    types_found_in_assembly = curr_assembly.GetTypes();
        //    foreach (var type in types_found_in_assembly)
        //    {
        //        _types_available.Add(type);
        //    }
        //    _types_available.OrderBy(t => t.FullName);
        //    OnAssembliesLoaded();
        //}
        //public void RemoveAssembly(string path)
        //{
        //    Assembly curr_assembly;
        //    Type[] types_found_in_assembly;
        //    curr_assembly = Assembly.ReflectionOnlyLoadFrom(path);
        //    types_found_in_assembly = curr_assembly.GetTypes();
        //    foreach (var type in types_found_in_assembly)
        //    {
        //        _types_available.Add(type);
        //    }
        //    _types_available.OrderBy(t => t.FullName);
        //    OnAssembliesLoaded();
        //}
    }
}
